package fr.emse.tatiana.replayable.tabulardisplay;

import java.io.File;
import java.io.IOException;
import java.util.Vector;



import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IInputValidator;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.ColumnViewerEditor;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationStrategy;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.EditingSupport;
import org.eclipse.jface.viewers.ILazyContentProvider;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableColorProvider;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerColumn;
import org.eclipse.jface.window.Window;
import org.eclipse.nebula.jface.gridviewer.GridTableViewer;
import org.eclipse.nebula.jface.gridviewer.GridViewerColumn;
import org.eclipse.nebula.jface.gridviewer.GridViewerEditor;
import org.eclipse.nebula.widgets.grid.Grid;
import org.eclipse.nebula.widgets.grid.GridColumn;
import org.eclipse.nebula.widgets.grid.GridItem;
import org.eclipse.swt.SWT;

import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.TextLayout;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;

import fr.emse.tatiana.ApplicationActionBarAdvisor;
import fr.emse.tatiana.TatianaConstants;

import fr.emse.tatiana.corpus.CorpusFile;
import fr.emse.tatiana.display.BadDisplayFile;
import fr.emse.tatiana.filter.CatWriter;
import fr.emse.tatiana.filter.FilterExecuter;
import fr.emse.tatiana.filter.FilterParameter;
import fr.emse.tatiana.filter.RemoveLines;
import fr.emse.tatiana.filter.TempFileFactory;
import fr.emse.tatiana.filter.XQuery;
import fr.emse.tatiana.replayable.AbstractReplayableEditor;
import fr.emse.tatiana.replayable.IReplayableModel;
import fr.emse.tatiana.replayable.IReplayableSaver;
import fr.emse.tatiana.replayable.ReplayableInput;
import fr.emse.tatiana.replayable.ReplayableModel;
import fr.emse.tatiana.replayable.analysis.IAnalysis;


public class ReplayableTabularViewer extends AbstractReplayableEditor implements IResourceChangeListener {




	public static final String ID = "fr.emse.tatiana.ReplayableTabularViewer";


	protected static final int TEXT_MARGIN = 2;


	private ReplayableInput input;
	protected IReplayableModel shower;
	protected int tablepageindex;
	protected int filterpageindex;
	protected GridTableViewer tableviewer;
	protected Grid table;

	protected ReplayableLabelProvider labelProvider;

	protected boolean updatewidths = true;
	protected final TextLayout layout = new TextLayout(null);



	public ReplayableTabularViewer() {
		super();
		ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
	}

	@Override
	public void dispose() {
		ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
		layout.dispose();
		super.dispose();
	}

	@Override
	public void init(IEditorSite site, IEditorInput input)
	throws PartInitException {

		super.init(site, input);

		if (input instanceof ReplayableInput) {
			this.input = (ReplayableInput) input;
		} else {
			throw new PartInitException("Bad input for Display");
		}
	}

	@Override
	protected void createPages() {
		createTablePage();
		createFilterPage();
		createLayoutPage();
		updateTablePage(); // do this only once all pages are created
		table.getParent().layout();
		int test = table.getClientArea().width;
	}

	private void createLayoutPage() {
		Composite composite = new Composite(getContainer(), SWT.NONE);
		Layout layout = new FillLayout();
		composite.setLayout(layout);

		Label layoutname = new Label(composite,SWT.LEFT);
		layoutname.setText(input.getLayoutName());

		int index = addPage(composite);
		setPageText(index, "Layout");
	}

	private void createFilterPage() {
		Composite composite = new Composite(getContainer(), SWT.NONE);
		Layout layout = new GridLayout(2,false);
		composite.setLayout(layout);

		new Label(composite, SWT.LEFT).setText("Filter: "+input.getFilterfile().getName());
		new Label(composite, SWT.LEFT);

		Vector<String> filterables = input.getCorpus().getFilterableFilePaths();

		Vector<FilterParameter> params = input.getParameters();

		for (final FilterParameter param : params) {

			Label label = new Label(composite, SWT.LEFT);
			label.setText(param.getLabel());

			final Integer pos = param.getNumber();

			if (FilterExecuter.ftype.equals(param.getType())) {

				// TODO z later make to handle default values
				final ComboViewer choiceviewer = new ComboViewer(composite,SWT.READ_ONLY);
				choiceviewer.setContentProvider(new IStructuredContentProvider() {

					public Object[] getElements(Object inputElement) {
						Vector<String> filterables = (Vector<String>) inputElement;
						return filterables.toArray(new String[]{});
					}

					public void dispose() {
					}

					public void inputChanged(Viewer viewer, Object oldInput,
							Object newInput) {
					}
				});

				choiceviewer.setLabelProvider(new LabelProvider() {

					/* (non-Javadoc)
					 * @see org.eclipse.jface.viewers.LabelProvider#getText(java.lang.Object)
					 */
					@Override
					public String getText(Object element) {
						String s = (String) element;
						return CorpusFile.XMLfileURIToName(s);
					}

				});

				choiceviewer.addSelectionChangedListener(new ISelectionChangedListener() {
					public void selectionChanged(SelectionChangedEvent event) {
						String value = (String) ((StructuredSelection) event.getSelection()).getFirstElement();
						if (input.setParam(param,value)) {
							firePropertyChange(PROP_DIRTY);
						}
					}		
				});

				choiceviewer.setInput(filterables);

				if (param.getValue() != null) {
					choiceviewer.setSelection(new StructuredSelection(param.getValue()));
				} else {
					// don't choose a default value
				}
			} else {
				// TODO z later add validation against type
				final Text choice = new Text(composite,SWT.BORDER | SWT.SINGLE);
				GridData griddata = new GridData();
				griddata.horizontalAlignment = GridData.FILL;
				choice.setLayoutData(griddata);
				choice.addModifyListener(new ModifyListener() {
					public void modifyText(ModifyEvent e) {
						input.setParam(param,choice.getText());
						firePropertyChange(PROP_DIRTY);
					}
				});
				if (param.getValue() != null) {
					choice.setText(param.getValue());
				} else if (param.getDefaultvalue()!=null) {
					choice.setText(param.getDefaultvalue());
					input.setParam(param,choice.getText());
					firePropertyChange(PROP_DIRTY);
				}
			}
		}

		

		Button runbutton = new Button(composite,SWT.NONE);
		runbutton.setText("Run Filter");
		
		final Button check = new Button(composite,SWT.CHECK);
		check.setText("clear cache (check this if a modification doesn't seem to be propagated in transformation)");
		runbutton.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent event) {
				input.runFilter(check.getSelection());
				updateTablePage();
				setActivePage(tablepageindex);
			}
		});
		


		filterpageindex = addPage(composite);
		setPageText(filterpageindex, "Filter");

	}

	/** Removes the current shower and creates a new one based on the 
	 *  state of the input. If the input may have changed (e.g. by the filterpage)
	 *  it must be re-executed prior to calling this method. All pages must
	 *  be created before calling this method.
	 * @throws BadDisplayFile 
	 */
	private void updateTablePage() {


		IReplayableModel m = input.getDisplayModel(analyses);
		if (m==null) {
			setActivePage(filterpageindex);
			return; // do nothing if filter isn't run yet
		}

		updatewidths = false;
		setShower(m);
		updatewidths = true;
		getContainer().layout(true, true);
		//tablecontainer.add(shower.table);
		setActivePage(tablepageindex);
		distributeWidths();

	}



	protected void addColumn(int idx) {
		GridColumn column = new GridColumn(table,SWT.LEFT);
		column.setText(shower.getFacetName(idx));
		column.setWordWrap(true);
		ViewerColumn vc = new GridViewerColumn(tableviewer,column);
		vc.setEditingSupport(new ReplayableEditingSupport(tableviewer,idx));
		tableviewer.setLabelProvider(labelProvider);
		column.pack();
	}

	protected void setShower(IReplayableModel theshower) {
		this.shower = theshower;
		while (table.getColumns().length>0) {
			table.getColumns()[0].dispose();
		}

		for(int col=0; col<shower.getFacetCount(); col++) {
			addColumn(col);
		}
		if (updatewidths) distributeWidths();
		updateColumnProperties();
		tableviewer.setInput(shower);
		tableviewer.refresh();

	}

	public void updateColumnProperties() {
		String[] properties = new String[shower.getFacetCount()];
		//CellEditor[] editors = new CellEditor[shower.getFacetCount()];
		for(int col=0; col<shower.getFacetCount(); col++) {
			properties[col] = new Integer(col).toString();
			//editors[col] = shower.getCellEditor(col,table);
		}
		tableviewer.setColumnProperties(properties);
		//tableviewer.setCellEditors(editors);
	}

	protected void distributeWidths() {
		int width = table.getClientArea().width-10;
	
		System.err.println("width:"+width);
		Vector<Integer> starcols = new Vector<Integer>();
		for(int col=0; col<shower.getFacetCount(); col++) {
			int w = shower.getPreferredWidth(col);
			if (w != 132) {
				width -= shower.getFontSize()*w;
				System.err.println("width:"+width);
				table.getColumn(col).setWidth(shower.getFontSize()*w);
				starcols.add(new Integer(col));
			} else {
				starcols.add(new Integer(col));
			}
		}
		if (width<100) width = 300; // reallocate room
		int j=0;
		for(int col : starcols) {
			int moy=0;
			int div=0;
			for(int i=0;i<shower.getEventCount();i++){
				if(labelProvider.getColumnText(shower.getEvent(i),j).length()!=0){
					moy+=labelProvider.getColumnText(shower.getEvent(i),j).length();
					div++;
				}	
			}
			System.err.println("col" + col + " width"+width/starcols.size());
			//table.getColumn(col).setWidth(width/starcols.size());
			if(div==0){
				div=1;
			}
			if(table.getColumn(col).getWidth()<10*moy/div){
				table.getColumn(col).setWidth(10*moy/div);
			}
			j++;
		}
	}

	protected void createTable(Composite c) {

		tableviewer = new GridTableViewer(c,/*SWT.VIRTUAL |*/ SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER | SWT.MULTI) {

			@Override
			protected void doSetSelection(Item[] items) {
				super.doSetSelection(items);
				doShowSelection();
			}

		}; // TODO check why GridTableViewer doesn't do this correctly.
		table = tableviewer.getGrid();
		table.setHeaderVisible(true);
		table.setLinesVisible(true);
		//tableviewer.setContentProvider(new LazyReplayableContentProvider());
		tableviewer.setContentProvider(new ReplayableContentProvider());	
		labelProvider = new ReplayableLabelProvider();
		tableviewer.setLabelProvider(labelProvider);
		//tableviewer.setCellModifier(new ReplayableCellModifier());
		tableviewer.setAutoPreferredHeight(true);
		table.setCellSelectionEnabled(true);

		ColumnViewerEditorActivationStrategy actSupport = new ColumnViewerEditorActivationStrategy(

				tableviewer) {

			@Override
			protected boolean isEditorActivationEvent(

					ColumnViewerEditorActivationEvent event) {

				boolean singleSelect = ((IStructuredSelection)getViewer().getSelection()).size() == 1;
				boolean isLeftMouseDouble = event.eventType == ColumnViewerEditorActivationEvent.MOUSE_CLICK_SELECTION && ((MouseEvent)event.sourceEvent).button == 1;
				boolean isNotMoveKey = event.eventType == ColumnViewerEditorActivationEvent.KEY_PRESSED 
				&& event.keyCode != SWT.ARROW_DOWN
				&& event.keyCode != SWT.ARROW_LEFT
				&& event.keyCode != SWT.ARROW_RIGHT
				&& event.keyCode != SWT.ARROW_UP;
				return singleSelect && (isLeftMouseDouble
						|| event.eventType == ColumnViewerEditorActivationEvent.PROGRAMMATIC
						|| event.eventType == ColumnViewerEditorActivationEvent.TRAVERSAL
						|| isNotMoveKey);
			}

		};



		GridViewerEditor.create(tableviewer, actSupport,

				ColumnViewerEditor.TABBING_HORIZONTAL

				| ColumnViewerEditor.TABBING_MOVE_TO_ROW_NEIGHBOR

				| ColumnViewerEditor.TABBING_VERTICAL

				| ColumnViewerEditor.KEYBOARD_ACTIVATION);
		
		
		createContextMenu();

	}

	
	private void createContextMenu() { 
		
		MenuManager menuMgr=new MenuManager(); 
		menuMgr.setRemoveAllWhenShown(true); 

		menuMgr.addMenuListener(new IMenuListener() { 
			public void menuAboutToShow(IMenuManager mgr) {
				for (IAnalysis analysis: analyses) {					
					mgr.add(analysis.getMenuManager(((StructuredSelection)tableviewer.getSelection()).toList(), shower));
				}
				
			}
		}); 
		// Create menu. 
		Menu menu = menuMgr.createContextMenu(tableviewer.getControl()); 
		tableviewer.getControl().setMenu(menu); 
		// Registermenu for extension. 

		getSite().registerContextMenu(menuMgr, tableviewer);
	}
	
	private void createTablePage() {
		Composite c = new Composite(getContainer(), SWT.NONE);

		c.setLayout(new org.eclipse.swt.layout.FillLayout());


		createTable(c);
//		viewer.addSelectionChangedListener(this);
//		viewer.addDoubleClickListener(this);

		// TODO figure out what this was supposed to do
//		c.addPaintListener(new PaintListener() {
//		public void paintControl(PaintEvent e) {
//		if (!widthsupdated ) {
//		distributeWidths();
//		widthsupdated = true;
//		}
//		}
//		});


		tablepageindex = addPage(c);
		setPageText(tablepageindex, "Replayable");

	}

	public void resourceChanged(IResourceChangeEvent event) {
		// TODO z later

	}



	@Override
	public void addAnalysis(IAnalysis a) {
		super.addAnalysis(a);
		if (shower != null) {
			int col = shower.getFacetIdFor(a);
			addColumn(col);
			updateColumnProperties();
			Object[] rows = shower.getAnalysedEvents(a);
			tableviewer.update(rows, null);
		}
	}

	@Override
	public void removeAnalysis(IAnalysis a) {
		table.getColumn(shower.getFacetIdFor(a)).dispose();
		super.removeAnalysis(a);
		updateColumnProperties();
	}

	private void hideAnalysis(IAnalysis a) {

	}

	private void showAnalysis(IAnalysis a) {

	}

	protected class LazyReplayableContentProvider implements ILazyContentProvider {

		public void updateElement(int index) {
			if (shower != null && index <= shower.getEventCount())
				tableviewer.replace(shower.getEvent(index), index);
		}

		public void dispose() {
			// do nothing
		}

		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
			if (newInput!=null) {
				tableviewer.setItemCount(shower.getEventCount());
			}
		}	
	}

	protected class ReplayableContentProvider implements IStructuredContentProvider {

		public void dispose() {
			// do nothing
		}

		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
			if (newInput!=null) {
				tableviewer.setItemCount(shower.getEventCount());
			}
		}

		public Object[] getElements(Object inputElement) {
			if (shower != null) {
				Vector<Object> ret = new Vector<Object>();
				for (int i = 0; i<shower.getEventCount(); i++) {
					ret.add(shower.getEvent(i));
				}
				return ret.toArray();
			}
			return null;
		}	
	}

	protected class ReplayableLabelProvider extends LabelProvider implements ITableLabelProvider, ITableColorProvider {

		public Image getColumnImage(Object element, int columnIndex) {
			return null;
		}

		public String getColumnText(Object element, int columnIndex) {
			String ret = shower.getFacetText(element,columnIndex);
			return (ret == null) ? "" : ret;
		}

		public Color getBackground(Object element, int columnIndex) {
			return shower.getBackground(element,columnIndex);
		}

		public Color getForeground(Object element, int columnIndex) {
			return shower.getForeground(element,columnIndex);
		}
	}

	protected CellEditor activeCellEditor;
	protected int activeColumn;

	protected class ReplayableEditingSupport extends EditingSupport {

		protected int column;

		public ReplayableEditingSupport(GridTableViewer viewer, int column) {
			super(viewer);
			this.column = column;
		}


		@Override
		protected boolean canEdit(Object element) {
			return shower.canModifyColumn(column);
		}

		@Override
		protected CellEditor getCellEditor(Object element) {
			activeColumn = column;
			activeCellEditor = shower.getCellEditor(column,table);
			return activeCellEditor;
		}

		@Override
		protected Object getValue(Object element) {
			return shower.getFacet(element,column);
		}

		@Override
		protected void setValue(Object element, Object value) {
			if (element instanceof GridItem) {
				element = ((GridItem) element).getData();
			}
			shower.setFacetValue(element, column,value);
			if (!getReplayableSaver().isSaved()) {
				firePropertyChange(PROP_DIRTY);
			}
			tableviewer.update(element, null);
		}



	}



	@Override
	protected void endSynchronisation() {
		try {
			tableviewer.removeSelectionChangedListener(this);
		} catch (Exception e) {
			// we are disposed
		}
	}

	@Override
	public IReplayableModel getModel() {
		return shower;
	}

	@Override
	public IReplayableSaver getReplayableSaver() {
		return input;
	}

	@Override
	protected void selectEvents(final StructuredSelection selection, final boolean scroll,
			final boolean propagate) {
		final ReplayableTabularViewer self = this;
		PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() {
			public void run() {
				try {
					if (!propagate) tableviewer.removeSelectionChangedListener(self);
					tableviewer.setSelection(selection, scroll);
				} finally {
					if (!propagate) tableviewer.addSelectionChangedListener(self);
				}
			}
		});

	}

	@Override
	protected void startSynchronisation() {
		tableviewer.addSelectionChangedListener(this);
	}

	@Override
	protected void updateTitle(int facet, String name) {
		table.getColumn(facet).setText(name);

	}

	@Override
	protected void updateViewer(Object[] rows) {
		tableviewer.update(rows, null);

	}

	@Override
	protected Object getEventFromSelection(Object o) {
		return o;
	}

	public void doSaveAsEditable() {
		CheckBoxInputDialog dialog = new CheckBoxInputDialog(getContainer().getShell(), 
				"Save replayable as...", 
				"Name of new replayable:", null,
				"Include open analyses", false, null);
		if (dialog.open() != Window.OK)
			return;
		doSaveAsEditable(dialog.getValue(),dialog.isChecked());
	}

	protected void doSaveAsEditable(String name,boolean includeanalysis) {
		ReplayableModel m = new ReplayableModel(getReplayableSaver().getProject());
		m.setContents(getModel(),includeanalysis);
		m.doSave(name);
	}

	protected void doExcelExport(String path) {
		if (getModel() == null)
		{
			System.err.println("Unable to save display file : table empty");
			return;
		}

		String qresult;
		try { 
			qresult=TempFileFactory.getName();
			ReplayableModel m = new ReplayableModel(getReplayableSaver().getProject());
			m.setContents(getModel(),true);
			m.saveToReplayableFile(qresult);
		} catch (IOException e) {
			// TODO Auto-generated catch block

			e.printStackTrace();
			return;
		}

		String removeresult="";

		try {
			removeresult=TempFileFactory.getName();
			IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(TatianaConstants.TATIANA_PROJECT_NAME);

			IFile filterfile = project.getFolder(TatianaConstants.SCRIPTS_FOLDER_NAME).getFile("disp2xcl.xq");
			new RemoveLines(new XQuery(
					filterfile.getLocation().toString(),
					new File(qresult).toURI().getPath()
			).getName(),
			removeresult,
			3,
			3
			);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		try {
			CatWriter writer = new CatWriter(path);
			IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(TatianaConstants.TATIANA_PROJECT_NAME);
			String base = project.getFolder(TatianaConstants.SCRIPTS_FOLDER_NAME).getLocation().toString();
			writer.add(base+"/XCL-begin.txt");
			writer.add(removeresult);
			writer.add(base+"/XCL-end.txt");
			writer.flush();
			writer.close();		
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		TempFileFactory.dispose();
	}

	public class CheckBoxInputDialog extends InputDialog {

		private String checkboxmessage;
		private boolean cvalue;
		private Button checkbox;

		public CheckBoxInputDialog(Shell parentShell, String dialogTitle,
				String dialogMessage, String initialValue,
				String checkboxMessage, boolean initialChecked,
				IInputValidator validator) {
			super(parentShell, dialogTitle, dialogMessage, initialValue, validator);
			checkboxmessage = checkboxMessage;
			cvalue = initialChecked;
		}

		@Override
		protected Control createDialogArea(Composite parent) {
			// TODO Auto-generated method stub
			Composite composite = (Composite) super.createDialogArea(parent);
			checkbox = new Button(composite, SWT.CHECK | SWT.BORDER);
			checkbox.setText(checkboxmessage);
			checkbox.setSelection(cvalue);
			GridData data = new GridData(GridData.GRAB_HORIZONTAL
					| GridData.GRAB_VERTICAL | GridData.HORIZONTAL_ALIGN_FILL
					| GridData.VERTICAL_ALIGN_CENTER);
			data.widthHint = convertHorizontalDLUsToPixels(IDialogConstants.MINIMUM_MESSAGE_AREA_WIDTH);
			checkbox.setLayoutData(data);
			checkbox.setFont(parent.getFont());

			return composite;
		}

		boolean isChecked() {
			return cvalue;
		}
		
	    @Override
		protected void buttonPressed(int buttonId) {
	        if (buttonId == IDialogConstants.OK_ID) {
	            cvalue = checkbox.getSelection();
	        }
	        super.buttonPressed(buttonId);
	    }

	}

}
